// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;

namespace Microsoft.Win32.SafeHandles
{
    /// <summary>
    /// Wrapper around a gss_name_t_desc*
    /// </summary>
    internal sealed class SafeGssNameHandle : SafeHandle
    {
        public static SafeGssNameHandle CreateUser(string name)
        {
            Debug.Assert(!string.IsNullOrEmpty(name), "Invalid user name passed to SafeGssNameHandle create");
            SafeGssNameHandle retHandle;
            Interop.NetSecurityNative.Status minorStatus;
            Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.ImportUserName(
                out minorStatus, name, Encoding.UTF8.GetByteCount(name), out retHandle);

            if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE)
            {
                retHandle.Dispose();
                throw new Interop.NetSecurityNative.GssApiException(status, minorStatus);
            }

            return retHandle;
        }

        public static SafeGssNameHandle CreateTarget(string name)
        {
            Debug.Assert(!string.IsNullOrEmpty(name), "Invalid target name passed to SafeGssNameHandle create");
            SafeGssNameHandle retHandle;
            Interop.NetSecurityNative.Status minorStatus;
            Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.ImportPrincipalName(
                out minorStatus, name, Encoding.UTF8.GetByteCount(name), out retHandle);

            if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE)
            {
                retHandle.Dispose();
                throw new Interop.NetSecurityNative.GssApiException(status, minorStatus);
            }

            return retHandle;
        }

        public override bool IsInvalid
        {
            get { return handle == IntPtr.Zero; }
        }

        protected override bool ReleaseHandle()
        {
            Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.ReleaseName(out _, ref handle);
            SetHandle(IntPtr.Zero);
            return status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE;
        }

        public SafeGssNameHandle()
            : base(IntPtr.Zero, true)
        {
        }
    }

    /// <summary>
    /// Wrapper around a gss_cred_id_t_desc_struct*
    /// </summary>
    internal sealed class SafeGssCredHandle : SafeHandle
    {
        private static readonly Lazy<bool> s_IsNtlmInstalled = new Lazy<bool>(InitIsNtlmInstalled);

        public static SafeGssCredHandle CreateAcceptor()
        {
            SafeGssCredHandle? retHandle;
            Interop.NetSecurityNative.Status status;
            Interop.NetSecurityNative.Status minorStatus;

            status = Interop.NetSecurityNative.AcquireAcceptorCred(out minorStatus, out retHandle);
            if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE)
            {
                throw new Interop.NetSecurityNative.GssApiException(status, minorStatus);
            }

            return retHandle;
        }

        /// <summary>
        ///  returns the handle for the given credentials.
        ///  The method returns an invalid handle if the username is null or empty.
        /// </summary>
        public static SafeGssCredHandle Create(string username, string password, Interop.NetSecurityNative.PackageType packageType)
        {
            if (packageType == Interop.NetSecurityNative.PackageType.NTLM && !s_IsNtlmInstalled.Value)
            {
                throw new Interop.NetSecurityNative.GssApiException(
                    Interop.NetSecurityNative.Status.GSS_S_BAD_MECH,
                    0,
                    SR.net_gssapi_ntlm_missing_plugin);
            }

            if (string.IsNullOrEmpty(username))
            {
                return new SafeGssCredHandle();
            }

            SafeGssCredHandle? retHandle = null;
            using (SafeGssNameHandle userHandle = SafeGssNameHandle.CreateUser(username))
            {
                Interop.NetSecurityNative.Status status;
                Interop.NetSecurityNative.Status minorStatus;
                if (string.IsNullOrEmpty(password))
                {
                    status = Interop.NetSecurityNative.InitiateCredSpNego(out minorStatus, userHandle, out retHandle);
                }
                else
                {
                    status = Interop.NetSecurityNative.InitiateCredWithPassword(out minorStatus, packageType, userHandle, password, Encoding.UTF8.GetByteCount(password), out retHandle);
                }

                if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE)
                {
                    retHandle.Dispose();
                    throw new Interop.NetSecurityNative.GssApiException(status, minorStatus);
                }
            }

            return retHandle;
        }

        public SafeGssCredHandle()
            : base(IntPtr.Zero, true)
        {
        }

        public override bool IsInvalid
        {
            get { return handle == IntPtr.Zero; }
        }

        protected override bool ReleaseHandle()
        {
            Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.ReleaseCred(out _, ref handle);
            SetHandle(IntPtr.Zero);
            return status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE;
        }

        private static bool InitIsNtlmInstalled()
        {
            return Interop.NetSecurityNative.IsNtlmInstalled();
        }
    }

    internal sealed class SafeGssContextHandle : SafeHandle
    {
        public SafeGssContextHandle()
            : base(IntPtr.Zero, true)
        {
        }

        public override bool IsInvalid
        {
            get { return handle == IntPtr.Zero; }
        }

        protected override unsafe bool ReleaseHandle()
        {
            Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.DeleteSecContext(out _, ref handle);
            SetHandle(IntPtr.Zero);
            return status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE;
        }
    }
}
